import json
import arrow
import re

from flask import request, jsonify
from bson import ObjectId

from app.libs.redprint import RedPrint
from app.libs.utility import get_request_args, filter_cha
from app.db import UpdateOne
from app.db.models.packages import Packages, LimitDefault
from app.db.models.users import User
from app.db.models.balances import Balances
from app.db.models.charges import Charges
from app.auth import permission_required
from app.log.logs import CPackages, UPackages
from app.validators.packages import get_checked_args, get_plans_feature, task_type_patterns
from app.libs.enums import ChargeType, LimitType, ENABLED_FUNC_TYPE
from app.libs.utils import balance_task_count_sync
from app.handler.v2_charges import ChargesHelper
from app.handler.v2_users import recharge_used
from app.errors import ParameterError

api = RedPrint('packages')


@api.route('/template/get', methods=['GET'])
@permission_required('admin')
def get_packages():
    allow_args = ('name', 'key', 'createTime', 'page', 'limit', 'chargeType')
    request_args = get_request_args(allow_args)

    name = filter_cha(request_args.get('name', ''))
    key = filter_cha(request_args.get('key', ''))
    create_time = request_args.get('createTime')
    page = request_args.get('page', 1)
    limit = request_args.get('limit', 10)
    charge_type = request_args.get('chargeType', '')

    query = {}
    if name:
        query['name'] = {'$regex': name}
    if key:
        query['key'] = {'$regex': key}
    if charge_type:
        query['chargeType'] = charge_type
    if create_time:
        create_time = create_time.split('_')
        query['createTime'] = {'$gte': arrow.get(create_time[0]).datetime, '$lt': arrow.get(create_time[1]).datetime}

    data = Packages.objects.find(query).order_by('-createTime').paginate(page, limit)
    new_data = []
    for item in data.items:
        _temp_data = item.to_mongo().to_dict()
        _temp_data['id'] = str(_temp_data.pop('_id'))
        _temp_data['createTime'] = arrow.get(_temp_data.get('createTime', arrow.utcnow())).isoformat()
        new_data.append(_temp_data)

    results = {
        'data': new_data,
        'count': data.total,
        'page': page
    }

    return jsonify({'code': 200, 'msg': 'ok', 'results': results})


@api.route('/template/get/old_format/plan', methods=['GET'])
@permission_required('admin')
def get_old_format_plan_packages():
    query = {'chargeType': 'plan'}
    if key := request.args.get('key'):
        query['key'] = filter_cha(key)
    packages = Packages.objects().filter(**query).only('name', 'key')
    return jsonify({
        'data': {
            "PlanMany": list(packages.as_pymongo())
        }
    })


@api.route('/template/get/old_format/addition-package', methods=['GET'])
@permission_required('admin')
def get_old_format_addition_package_packages():
    query = {'chargeType': 'additionPackage'}
    if key := request.args.get('key'):
        query['key'] = filter_cha(key)
    packages = Packages.objects().filter(**query).only('name', 'key')
    return jsonify({
        'data': {
            "AdditionPackageMany": list(packages.as_pymongo())
        }
    })


@api.route('/template/add', methods=['POST'])
@permission_required('admin')
def add_packages():
    request_args = json.loads(request.data)

    request_args = get_checked_args(request_args)

    Packages().from_dict(request_args).save()
    msg = '添加成功'

    CPackages().info(
        action_message='后台定制套餐/模块添加',
        detail=f"套餐/模块名称: {request_args.get('name')}, "
               f"英文名称: {request_args.get('key')}, "
               f"操作人: {request_args.get('operator')}"
    )

    return jsonify({'code': 200, 'msg': msg})


@api.route('/template/default/get', methods=['GET'])
@permission_required('admin')
def get_add_package_default():
    data = LimitDefault().to_mongo().to_dict()
    allow_keys = ('total', 'limitType')

    limit = {}
    for func_type, item in data.items():
        _temp_dict = {}
        for _k, _v in item.items():
            if _k in allow_keys:
                _temp_dict[_k] = _v

        limit[func_type] = _temp_dict

    results = {
        'limit': limit
    }
    return jsonify({'code': 200, 'msg': 'ok', 'results': results})


@api.route('/user/get', methods=['GET'])
@permission_required('admin')
def get_user_packages():
    allow_args = ('uid', 'username')
    utility_keys = ("chargeType", "funcType", "limitType", "taskType", "total", "number", 'used')
    request_args = get_request_args(allow_args)

    username = request_args.get('username')
    uid = request_args.get('uid')

    if (not username and not uid) or not (user := User.objects.find(
            {'$or': [{'username': username}, {'_id': ObjectId(uid)}]}).first()):
        return jsonify({'code': 200, 'msg': 'ok', 'results': {}})

    recharge_used(user.id)
    balance = Balances.objects.find({'uid': user.id}).first()
    pack_id_name_map = {item.id: {'name': item.name, 'feature': item.feature} for item in Packages.objects.find()}

    plan_package = pack_id_name_map.get(balance.planId, {})
    plan_name = plan_package.get('name', '')

    addition_packages = {}
    plan = {}
    for charge in ChargesHelper({'uid': user.id}).get():
        _temp_dict = {}
        for k in utility_keys:
            _temp_dict[k] = getattr(charge, k, '')

        _temp_dict['expireTime'] = arrow.get(charge.expireTime).isoformat() if charge.expireTime else ''
        _temp_dict['createTime'] = arrow.get(charge.createTime).isoformat()
        _temp_dict['packId'] = str(charge.packId)
        _temp_dict['_id'] = str(charge.id)
        _temp_dict['name'] = pack_id_name_map.get(charge.packId, {}).get('name', '')

        if charge.chargeType == ChargeType.plan.value:
            plan[charge.funcType] = _temp_dict
        else:
            _create_time_pack_id = f"{charge.createTime}_{charge.packId}"
            addition_packages.setdefault(_create_time_pack_id, []).append(_temp_dict)

    results = {
        'plan_name': plan_name,
        'plan': plan,
        'feature': balance.feature,
        'additionPackages': list(addition_packages.values()),
        'planChargeTime': arrow.get(balance.planChargeTime).isoformat() if balance.planChargeTime else "",
        'planExpireTime': arrow.get(balance.planExpireTime).isoformat() if balance.planExpireTime else "",
        'uid': str(user.id),
        '_id': str(balance.id),
        'planId': str(balance.planId),
        'marks': balance.marks
    }

    return jsonify({'code': 200, 'msg': 'ok', 'results': results})


@api.route('/user/edit', methods=['POST'])
@permission_required('admin')
def edit_user_packages():
    """
    自定义修改用户套餐
    1、一次只能修改一个用户数据，且用户必须已经购买买过套餐，免费用户无法修改，同时也不能修改过期时间
    2、修改用户购买套餐数据，实际是修改charges表中购买套餐时的记录数据, 比如web资产数量、是否支持IPv6监测、web漏洞监测任务执行频率
    3、独立模块只能删除，暂时不支持添加与修改。 比如二次开发API模块、域名包模块等
    4、最后需要结合changes表中购买套餐记录、购买模块记录更新balances表数据，记下用户当前实际情况
    """
    request_args = json.loads(request.data)
    del_addition_packages = request_args.get('delAdditionPackages', [])
    plans = request_args.get('plans', [])
    marks = request_args.get('marks', '')
    operator = request_args.get('operator')
    now_arrow_time = arrow.utcnow()
    if not marks:
        return ParameterError(msg="'备注' 为必填项!")
    charge_ids = []
    not_charge_err = "用户至少需要购买过一次套餐才能定制修改"
    if del_addition_packages:
        charge_ids.extend(del_addition_packages)
    if plans:
        charge_ids.extend([p["_id"] for p in plans if p.get("_id")])
    if not charge_ids:
        return ParameterError(msg=not_charge_err)
    user_ids = Charges.objects.filter(id__in=charge_ids).distinct("uid")
    if not user_ids:
        return ParameterError(msg=not_charge_err)
    if len(user_ids) > 1:
        return ParameterError(msg="只能选择同一个用户购买的套餐、模块定制修改")
    uid = user_ids[0]

    # 标记删除本次删除的独立模块
    packages_list = []
    for addition in ChargesHelper({'_id': {'$in': [ObjectId(_id) for _id in del_addition_packages]}}).get():
        packages_list.append((addition.packId, addition.createTime))
    packages_set = set(packages_list)
    addition_update_list = []
    for item in packages_set:
        for addition in ChargesHelper(
                {'uid': uid, 'packId': item[0], 'createTime': item[1], 'isDelete': False}).get():
            addition_update_list.append(UpdateOne({'_id': addition['id']}, {'$set': {
                'isDelete': True,
                'updateTime': now_arrow_time.datetime
            }}))
    if addition_update_list:
        Charges.objects.bulk_write(addition_update_list)

    balance = Balances.objects().filter(uid=uid).first()
    expire_time = balance.planExpireTime
    # 开启的模块
    enable_features = set()
    # 更新charges
    for plan in plans:
        _total = plan.get('total')
        func_type = plan.get('funcType', '')
        use_enable = True if func_type in ENABLED_FUNC_TYPE else False
        if (res := re.match(task_type_patterns, func_type)) and (not plan.get("taskType")):
            plan["taskType"] = res.group()
        if not hasattr(balance.balance, func_type):
            continue
        limit_type = getattr(balance.balance, func_type).limitType
        plan["limitType"] = limit_type
        if _total is None:
            continue
        if 'Enable' in func_type:
            continue
        # 没有支持修改API二次开发、扫描时间控制等定时过期模块，以下逻辑可行。后期如果需要支持得特殊处理
        if limit_type == LimitType.exp.value:
            continue
        _total = int(_total)
        if _total:
            enable_features.add((func_type, limit_type))
        # Upsert collection charges.
        # 没有_id 表示之前没有购买过， 需要生成charges购买记录
        if not (_charge_id := plan.get('_id')):
            if "createTime" not in plan:
                plan.update({"createTime": arrow.utcnow().datetime})
            Charges().from_dict({
                **plan, 'uid': uid, 'packId': balance.planId,
                'expireTime': expire_time
            }).save()
            if use_enable:
                Charges().from_dict({
                    **plan, 'funcType': f"{func_type}Enable",
                    'uid': uid, 'packId': balance.planId, 'expireTime': expire_time
                }).save()
        else:
            object_plan = ChargesHelper({'_id': ObjectId(_charge_id)}).get().first()
            object_plan.update(total=_total, limitType=plan.get('limitType'))
            if use_enable:
                enable_object_plan = ChargesHelper({
                    'chargeType': 'plan',
                    'createTime': object_plan['createTime'],
                    'funcType': f"{func_type}Enable"
                }).get().first()
                if enable_object_plan:
                    enable_object_plan.update(total=_total, limitType=plan.get('limitType'))
                else:
                    object_plan.reload()
                    item = object_plan.to_mongo().to_dict()
                    del item["_id"]
                    item["funcType"] = f"{func_type}Enable"
                    Charges().from_dict(item).save()

    balance_update_dict = {"marks": marks}
    # 根据最新的charges计算更新balances数据
    for charge in Charges.objects.filter(uid=uid, isDelete=False):
        func_type = charge.funcType
        used = charge.used or 0
        _time = charge.time or 0
        total = charge.total or 0
        limit_type = charge.limitType
        if (
                (limit_type != LimitType.exp.value and not total) or
                (limit_type == LimitType.exp.value and _time < arrow.utcnow().int_timestamp)
        ):
            continue
        # API二次开发、扫描时间控制特别处理
        if func_type in ["wsTaskTime", "api"]:
            enable_features.add((func_type, limit_type))
        # 执行频率取最快, 只用记录频率值
        if limit_type == LimitType.frequency.value:
            if last_v := balance_update_dict.get(f"set__balance__{func_type}__total"):
                balance_update_dict[f"set__balance__{func_type}__total"] = min([last_v, total])
            else:
                balance_update_dict[f"set__balance__{func_type}__total"] = total
        # 到点过期的模块取最大时间
        elif limit_type == LimitType.exp.value:
            if last_v := balance_update_dict.get(f"set__balance__{func_type}__time"):
                balance_update_dict[f"set__balance__{func_type}__time"] = max([last_v, _time])
            else:
                balance_update_dict[f"set__balance__{func_type}__time"] = _time
        # 以数量限制的模块，取多次的和, 已经使用的数量也需要加和
        elif limit_type == LimitType.limit.value:
            if last_v := balance_update_dict.get(f"set__balance__{func_type}__total"):
                balance_update_dict[f"set__balance__{func_type}__total"] = last_v + total
                last_used = balance_update_dict[f"set__balance__{func_type}__used"]
                balance_update_dict[f"set__balance__{func_type}__used"] = last_used + used
            else:
                balance_update_dict[f"set__balance__{func_type}__total"] = total
                balance_update_dict[f"set__balance__{func_type}__used"] = used
        # 周期重置数量(period)，最大数量(max), 最长周期(duration), 这个三类模块， 取最大值
        elif limit_type in [LimitType.period.value, LimitType.max.value, LimitType.duration.value]:
            if last_v := balance_update_dict.get(f"set__balance__{func_type}__total"):
                balance_update_dict[f"set__balance__{func_type}__total"] = max([last_v, total])
            else:
                balance_update_dict[f"set__balance__{func_type}__total"] = total
    balance_update_dict.update({'feature': get_plans_feature(enable_features)})
    balance.update(**balance_update_dict)
    # 补齐mongodb中字段
    balance.reload()
    Balances._get_collection().update_one({"_id": balance.id}, {"$set": balance.to_mongo().to_dict()})
    # 同步一下余额
    balance.reload()
    balance_task_count_sync(balance.uid, balance)

    UPackages().info(
        action_message='后台定制套餐修改',
        detail=f"uid:{uid}, "
               f"删除模块: {del_addition_packages}, "
               f"修改套餐: {plans}, "
               f"操作人: {operator}"
    )
    return jsonify({'code': 200, 'msg': 'ok'})
